iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 7
3
AI & Data

資幾資比系列 第 7

[Series - 6] EDA介紹

  • 分享至 

  • xImage
  •  

前言

此篇文是由 Joyce 所撰寫


EDA(Exploratory data analysis)

EDA翻譯成中文是探索式資料分析,是在對資料進行前處理,
主要的功能分成兩種,第一種是描述性統計,第二種是透過圖表觀察。不管是哪一種,它背後的數學技巧都是很基礎的,操作起來也很簡單,但是千萬不要因此小看EDA的威力,它可以讓我們在分析數據前,先對資料的分布、型態,有無離群值,有個大概的了解。

  • 數據的趨勢
  • 數據的分布
  • 異常值的出現
  • 重要參數的挑選
  • 基本假設的檢查
  • 評估分析方向

描述性統計

描述性統計的資料分為兩種,一種是集中量數,用來描述數據的集中程度,另一種是離散量數,則是用來描述離散程度,可以透過這些統計值,對資料的分佈有初步的認識。

  1. 了解離散程度
  2. 特徵值的選取
  3. 缺失值的發現

集中量數

常見的集中量數為眾數、中位數、平均數,用來觀察資料是否集中,而除此之外,我們也常常用這三個數討論為常態分佈還是偏態分布。可以透過三個述職的大小加上圖形分析決定為哪一種分布。

  1. 眾數 : 數據中出現次數最多的數值。
  2. 中位數 : 數據中排序最中間的數值。
  3. 平均數 : 數據加總除以總個數的數值。

  • 負偏態 : 左右不對稱,眾數偏右,眾數>中位數>平均數。
  • 常態 : 左右對稱,眾數在中間,眾數=中位數=平均數。
  • 正偏態 : 左右不對稱,眾數偏左,眾數<中位數<平均數。

離散量數

常見的離散量數有最大(小)值、四分位差、標準差,可以透過這些數值,查看資料的分散性。

  1. 最大(小)值 : 數據中最大(小)的數值。
  2. 範圍 : 數據中最大值與最小值的差。
  3. 四分位差 : 數據中第三個四分位數和第一個四分位數的差值。
  4. 標準差 :公式 $SD=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(x_i-\mu)^2}$,其中 $\mu$ 是指平均值。

圖表觀察

除了透過數值的觀察,最直接的方式是透過視覺化的圖形,像是趨勢圖、散佈圖,讓數據的走向、型態一目了然。

  1. 了解資料分布
  2. 檢視基本假設
  3. 延伸新的研究方向

趨勢圖

將數據用趨勢圖的方式呈現,為了方便分析,常常我們會加入迴歸線,並附上迴歸函數,調整參數讓迴歸線貼近趨勢圖。觀察下面這張圖,我們可以推測當x軸越大,對應的y值也會越大,呈正相關。

累積圖

觀察累積圖的分布可以一眼看出集中區域,像是此圖size2就特別的多,因為他的斜率最大,這樣一來,我們就知道size2是我們需要特別注意觀察的對象。

散佈圖

將數據用散佈圖的方式呈現,可以一眼看出是否有數據是異常值、極端值,數據是否能有一區一區的分類。

直方圖

透過直方圖的觀察,可以看出是否呈現常態分佈,也可以一眼看出眾數、最大(小)值落在哪個區間。

盒方圖

繪製盒方圖可以看出數據的離散程度,且也可以看出最大(小)值,及四分位差。


進階分析

透過選擇適當的EDA分析,會對數據有一定的了解。那麼接下來就要就要更深入地去思考數據背後的意義。舉些小例子,

  1. 身為一個衣服批發商,隻身前往歐洲,手上有一萬比歐洲人的衣服尺碼,他可以先透過眾數,盒方圖…的分析,決定衣服S、M、L需要多大,因為眾數就代表群眾的身材型態。
  2. 利用值方圖查看歷年來的死亡人數,發現1900(舉例)年的死亡人數特別的高,可以去思考是否那年發生了什麼重大的天災人禍。

如果數據沒有錯誤,那麼每一筆數據都有其背後值得分析的意義,像是特徵值的選取,異常值分析,分布情形,分析完後,要怎麼去解釋,就要看分析者的功力了。


實際舉例

實作連結

colab連結

資料取得

我們用IMMC 2019 中華賽 秋季賽 B 題的題目進行舉例,先將csv檔下載下來。

import pandas as pd # 引入pandas
data = pd.read_csv("weboggle_immc_selected.csv") # 讀取資料

原始數據形式

數據解釋

  1. 實驗數據提供以下資訊:
    • 玩家代號: num_id
    • 伺服器記錄之時間: time_stamp
    • 該玩家所在時區與伺服器時間之時差 : zone(單位: 分鐘)
    • 該輪遊戲之答對題數(correct)、共猜測題數(guess)、得分(score)
    • 該輪遊戲之時間編號:session(因此編號相同的遊戲之 time_stamp 必定間隔 3 分 45 秒,並可由”有幾個連續的相同編號”得知該節遊戲共玩幾輪)
    • 承諾機制類型:commit_type (1 表示該節遊戲前之承諾,2 表示該節遊戲中之承諾, 1.5 表示該節遊戲前且遊戲中均承諾設限)
    • 承諾輪數:commit_limit (若 commit_type=1,則此數字表示該節遊戲前承諾要玩幾輪; 若 commit_type=2,由於遊戲中之承諾輪數永遠為 1,故此數字必為 1;若 commit_type=1.5,則此數字表示該節遊戲前承諾要玩幾輪)
    • 該節遊戲是否出現設限對話框:treat_dummy(0 表示不會遇到詢問設限的對話框;1 表示會遇到詢問設限的對話框)
  2. 實驗數據橫跨 84 個月,前 30 個月沒有設承諾機制,後 54 個月有設定承諾機制。
  3. 實驗對象共 10000 人
    • 對照組有 5000 人,從未遇過詢問設限的對話框(故 treat_dummy 總是為 0)。
    • 實驗組有 5000 人,將經歷未設承諾機制時期到設承諾機制時期(故有些人 treat_dummy 從 0 到 1 都有,有些人 treat_dummy 則始終為 1)。

EDA分析

基本設定檢查

  1. ID : 題目提及人數為10000人,因此我們先檢查是否真的為10000人。由輸出結果可知是10000人沒錯。
len(data["num_id"].unique())

output

10000

異常值

  1. commit_type : 依照數據解釋,commit_type只會有沒有值11.52,四種可能值。使用unique()的函數檢查所有可能值,由輸出結果可知commit_type的值可知不合理數字共有 **1.3333…**及 **1.6666…**兩種,可以考慮刪除這些資料。
data["commit_type"].unique() # 查看不重複的元素

output

array([ nan, 1., 2., 1.5, 1.3333334, 1.6666666])
  1. commit_limit : limit是場數限制的意思,亦即輸入的值要可以真的發揮到限制的功用才行。由輸出結果可以看出有極少數玩家輸入的 commit_limit 數值顯然無參考價值,,實際上根本不可能玩這麼多場數,可以考慮以沒有輸入來取代這些值。
data["commit_limit"].unique()

output

array([1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00,
       5.000000e+00, 6.000000e+00, 7.000000e+00, 8.000000e+00,
       9.000000e+00, 1.000000e+01, 1.100000e+01, 1.200000e+01,
       1.300000e+01, 1.400000e+01, 1.500000e+01, 1.600000e+01,
       1.700000e+01, 1.800000e+01, 1.900000e+01, 2.000000e+01,
       2.200000e+01, 2.300000e+01, 2.400000e+01, 2.500000e+01,
       2.700000e+01, 2.900000e+01, 3.000000e+01, 3.100000e+01,
       3.200000e+01, 3.300000e+01, 3.500000e+01, 4.000000e+01,
       4.100000e+01, 4.300000e+01, 4.400000e+01, 4.700000e+01,
       5.000000e+01, 5.100000e+01, 5.400000e+01, 5.500000e+01,
       6.000000e+01, 6.100000e+01, 6.500000e+01, 7.000000e+01,
       7.100000e+01, 7.200000e+01, 7.400000e+01, 7.500000e+01,
       7.700000e+01, 8.000000e+01, 8.100000e+01, 8.200000e+01,
       8.800000e+01, 9.000000e+01, 9.100000e+01, 9.200000e+01,
       9.300000e+01, 9.500000e+01, 9.600000e+01, 9.900000e+01,
       1.000000e+02, 1.110000e+02, 1.400000e+02, 1.990000e+02,
       2.000000e+02, 2.220000e+02, 2.500000e+02, 3.000000e+02,
       4.000000e+02, 5.000000e+02, 5.400000e+02, 5.550000e+02,
       5.670000e+02, 6.000000e+02, 6.570000e+02, 6.660000e+02,
       6.750000e+02, 6.780000e+02, 7.650000e+02, 7.770000e+02,
       7.890000e+02, 8.730000e+02, 8.760000e+02, 8.880000e+02,
       9.000000e+02, 9.830000e+02, 9.870000e+02, 9.990000e+02,
       1.000000e+03, 1.200000e+03, 1.234000e+03, 4.234000e+03,
       4.523000e+03, 5.000000e+03, 5.643000e+03, 7.895000e+03,
       7.898000e+03, 8.888000e+03, 9.876000e+03, 9.999000e+03,
       1.000000e+04, 1.234500e+04, 2.000000e+04, 5.000000e+04,
       7.777700e+04, 8.765400e+04, 9.876500e+04, 9.999900e+04,
       1.000000e+05, 1.111110e+05, 1.234540e+05, 1.234560e+05,
       7.654320e+05, 7.777770e+05, 8.765430e+05, 8.888880e+05,
       9.876540e+05, 9.999990e+05, 1.000000e+06, 4.444444e+06,
       5.000000e+06, 7.777777e+06,          nan])

數據分布

  1. ?i (每個人共玩?i個 session)之分布情形
    分析每個玩家遊玩過幾個session,並輸出為count.csv
# count the number of sessions for each ID
data_reduce = data.drop_duplicates(subset=["session"])
result = data_reduce.groupby("num_id").count()["session"]

# output count_session to count.csv
result.to_csv("count.csv", index=False)

繪製成直方圖,由下方的直方圖可知因為局數的範圍太大了,不太好做分析。

import matplotlib.pyplot as plt # 引入matplotlib的函數

result_session = pd.read_csv("count.csv")

plt.title("Total session")
plt.xlabel("Si : sessions")     
plt.ylabel("numbers of people")
plt.hist(result_session["session"])# 繪製直方圖
plt.show() # 顯現圖形 


因此我們把局數縮小到只考慮20局以內。考慮20局以內的玩家,可以由下圖看出玩家的分布大概呈指數下降。

list_20 = []
for _ in range(1,21):
    list_20.append(0)

result_session = pd.read_csv("count.csv")

for i in result_session["session"]:
  if(i <= 20):
      list_20[i-1] += 1
count = list(range(1,21))
plt.title("Distribution (1 ≦ Si ≦ 20)")
plt.xlabel("Si : sessions")     
plt.ylabel("numbers of people")
plt.plot(count, list_20)# 繪製直方圖
plt.show() # 顯現圖形 

  1. ?i(每個人接觸此遊戲的總歷時)之分布情形
    分析每個玩家在這個遊戲的總歷時,並輸出為time.csv
# split each Id
num_id = data["num_id"].unique()

# creat an empty dictionary
time_data = {"num_id": [], "total_time": []}

for num in num_id:
    id_temp = data[data["num_id"] == num]
    length = len(id_temp)
    
    # get each ID first play time and last play time
    time_first = id_temp.iloc[0, 1]
    time_last = id_temp.iloc[length - 1, 1]
    
    # add blank space to time string
    time_first = time_first[:2] + ' ' + time_first[2:5] + ' ' + time_first[5:]
    time_last = time_last[:2] + ' ' + time_last[2:5] + ' ' + time_last[5:]
    
    # change time string to standard format
    timeArray_first = time.strptime(time_first, "%d %b %Y %H:%M:%S")
    timeArray_last = time.strptime(time_last, "%d %b %Y %H:%M:%S")
    
    # chage the standard format to seconds
    first_second = int(time.mktime(timeArray_first))
    last_second = int(time.mktime(timeArray_last))
    
    # calculate the difference and convert to year
    # 31536000 seconds = 1 year
    total = (last_second - first_second)/31536000
    
    # save to dictionary
    time_data["num_id"].append(num)
    time_data["total_time"].append(total)

# output to the file
result = pd.DataFrame(time_data)    
result.to_csv("time.csv", index=False)  

繪製成直方圖

result_time = pd.read_csv("time.csv")

plt.title("Total time")
plt.xlabel("Si : time")     
plt.ylabel("numbers of people")
plt.hist(result_time["total_time"])# 繪製直方圖
plt.show() # 顯現圖形 

思考

看到數據的分布情形,我們可以將資料分群,像是分兩群,短期玩家及長期玩家,或是分三群,過客型玩家、短期成癮玩家、長期穩定型玩家,該如何分析就需要自己決定了。還有很多的參數,我們沒有進行檢查或分析,這裡只舉些小小的分析供大家學習。


結語

EDA是由有著統計界畢卡索之稱的John Tukey提出的,以下是他的經典名言

"An approximate answer to the right question is worth a great deal more than a precise answer to the wrong question."
對正確的問題有個近似的答案,勝過對錯的問題有精確的答案。

利用EDA的分析,讓我們能在處理問題、分析數據上能更加有把握。


參考資料


上一篇
[Series - 5] 資料種類介紹
下一篇
[Series - 7] 特徵工程介紹
系列文
資幾資比31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言